// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.

#define private public
#define protected public

#import <objc/runtime.h>

#include <memory>

#import <Lynx/LynxErrorBehavior.h>
#import <Lynx/LynxTemplateRender.h>
#import <Lynx/LynxView+Internal.h>
#import <Lynx/LynxView.h>
#import "LynxBackgroundRuntime+Internal.h"
#import "LynxTemplateRender+Protected.h"
#import "LynxUnitTestUtils.h"

#include "core/resource/lynx_resource_loader_darwin.h"
#include "core/shell/lynx_shell.h"
#include "core/shell/runtime_mediator.h"
#include "core/shell/runtime_standalone_helper.h"

@interface Mock2GenericResourceFetcher : NSObject <LynxGenericResourceFetcher>

@end

@implementation Mock2GenericResourceFetcher

- (dispatch_block_t)fetchResource:(LynxResourceRequest*)request
                       onComplete:(LynxGenericResourceCompletionBlock)callback {
  return nil;
}

- (dispatch_block_t)fetchResourcePath:(LynxResourceRequest*)request
                           onComplete:(LynxGenericResourcePathCompletionBlock)callback {
  return nil;
}

@end

@interface MockLynxViewClient : NSObject <LynxViewLifecycle, LynxBackgroundRuntimeLifecycle>

- (instancetype)init:(XCTestExpectation*)expectation;

@end

@implementation MockLynxViewClient {
  XCTestExpectation* _expectation;
}

- (instancetype)init:(XCTestExpectation*)expectation {
  if (self = [super init]) {
    _expectation = expectation;
  }
  return self;
}

- (void)lynxView:(LynxView*)view didRecieveError:(NSError*)error {
  [self checkLynxError:error];
}

- (void)runtime:(LynxBackgroundRuntime*)runtime didRecieveError:(NSError*)error {
  [self checkLynxError:error];
}

- (void)checkLynxError:(NSError*)error {
  if ([error code] == EBLynxResourceExternalResource) {
    [_expectation fulfill];
  }
}

@end

@interface LynxTemplateRender (UnitTest)

- (lynx::shell::LynxShell*)getShell;

@end

@implementation LynxTemplateRender (UnitTest)

- (lynx::shell::LynxShell*)getShell {
  return shell_.get();
}

@end

@interface LynxResourceLoaderDarwinUnitTest : LynxUnitTest
@property XCTestExpectation* onErrorExpectation;
@end

@implementation LynxResourceLoaderDarwinUnitTest

- (void)testLoadBytecode {
  Mock2GenericResourceFetcher* genericFetcher = [[Mock2GenericResourceFetcher alloc] init];
  auto loader =
      std::make_shared<lynx::shell::LynxResourceLoaderDarwin>(nil, nil, nil, nil, genericFetcher);
  auto request = lynx::pub::LynxResourceRequest{"bytecode_url",
                                                lynx::pub::LynxResourceType::kExternalByteCode};
  std::promise<std::string> promise;
  std::future<std::string> future = promise.get_future();
  loader->LoadBytecode(
      request, [promise = std::move(promise)](lynx::pub::LynxResourceResponse& response) mutable {
        promise.set_value(response.err_msg);
      });

  if (future.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
    XCTAssertTrue(false);
  }
  XCTAssertTrue(future.get().length() > 0);
}

- (void)testReportErrorToLynxView {
  // create a LynxView and LynxViewClient
  _onErrorExpectation = [self expectationWithDescription:@"onReceiveError"];
  LynxView* lynxView = [[LynxView alloc] init];
  MockLynxViewClient* client = [[MockLynxViewClient alloc] init:_onErrorExpectation];
  [lynxView addLifecycleClient:client];

  // invoke LynxResourceLoader.reportError
  lynx::shell::LynxShell* shell = [[lynxView templateRender] getShell];
  [self invokeReportError:shell->runtime_actor_];

  // wait to receive error
  [self await];
}

- (void)testReportErrorToBackground {
  // create a BackgroundRuntime and BackgroundRuntimeClient
  _onErrorExpectation = [self expectationWithDescription:@"onReceiveError"];
  LynxBackgroundRuntime* backgroundRuntime =
      [[LynxBackgroundRuntime alloc] initWithOptions:[[LynxBackgroundRuntimeOptions alloc] init]];
  MockLynxViewClient* client = [[MockLynxViewClient alloc] init:_onErrorExpectation];
  [backgroundRuntime addLifecycleClient:client];

  // invoke LynxResourceLoader.reportError
  [self invokeReportError:[backgroundRuntime runtimeActor]];

  // wait to receive error
  [self await];
}

- (void)await {
  [self waitForExpectationsWithTimeout:5
                               handler:^(NSError* error) {
                                 if (error) {
                                   NSLog(@"Timeout Error: %@", error);
                                 }
                               }];
}

- (void)invokeReportError:
    (const std::shared_ptr<lynx::shell::LynxActor<lynx::runtime::LynxRuntime>>&)runtime {
  lynx::runtime::TemplateDelegate* delegate = runtime->impl_->delegate_.get();
  lynx::shell::RuntimeMediator* runtime_mediator =
      static_cast<lynx::shell::RuntimeMediator*>(delegate);
  runtime_mediator->external_resource_loader_->LoadJSSource("test");
}

@end
